#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <UIKit/UIApplication.h>
#import <SpringBoard/SBApplicationController.h>
#import <SpringBoard/SBApplication.h>
#import <LockInfo/Plugin.h>

#import "framework/FWCommon.h"
#import "framework/FWPlugin.h"
#import "framework/FWApplication.h"

#import "PICommon.h"
#import "PIPlugin.h"
#import "PIIntegration.h"


@implementation PIPlugin

@synthesize appURLScheme, appDisplayIdentifier, appBundleIdentifier, pocketInformantPresent, pocketInformantFull, dbVersion, dbEntityKeys, pocketInformantLastSeen;


// ******************************************************************************************
#pragma mark -
#pragma mark Initialisation and object management

- (id)initWithApplication:(FWApplication*)app name:(NSString *)name
{
    if ((self = [super initWithApplication:app name:name])) {
        app.applicationDelegate = self;
        self.pocketInformantLastSeen = [NSDate date];
        
        //NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
        //[center addObserver:self selector:@selector(update:) name:UIPasteboardChangedNotification object:nil];
    }        
    
    return self;
}

- (id)initAsPIPresent
{
    if ((self = [super init])) {
        pocketInformantPresent = YES;
    }        
    
    return self;
}

- (id)initWithPIVersion:(NSInteger)vers
{
    if ((self = [super init])) {
        pocketInformantPresent = YES;
        dbVersion = (PIDatabaseVersion)vers;
    }        
    
    return self;
}

- (void)dealloc
{
    [appBundleIdentifier release];
    [appURLScheme release];
    [appDisplayIdentifier release];
    [dbEntityKeys release];
    
    [super dealloc];
}


// ******************************************************************************************
#pragma mark -
#pragma mark Checking database versions 

static NSInteger const kPI2VersionSQL = 1;
static NSInteger const kPI2MetaDataSQL = 2;
static NSInteger const kPI2TableDataSQL = 3;


- (NSObject*)objectForSQLRecord:(sqlite3_stmt*)sqlStatement withId:(NSInteger)sqlid context:(NSObject*)context application:(FWApplication*)application
{
    if (sqlid == kPI2VersionSQL) {
        NSInteger key = sqlite3_column_int(sqlStatement, 0);
        NSString* name = FWDBReadColumnString(sqlStatement, 1);
        return [NSArray arrayWithObjects:[[NSNumber numberWithInt:key] stringValue], name, nil];
    }
    else if (sqlid == kPI2MetaDataSQL) {
        //NSString* uuid = FWDBReadColumnString(sqlStatement, 0);
        //NSObject* plist = FWDBReadColumnBlob(sqlStatement, 1);
        return @"OK";
    }
    else if (sqlid == kPI2TableDataSQL) {
        NSNumber* count = [NSNumber numberWithInt:sqlite3_column_int(sqlStatement, 0)];
        return count;
    }
    
    return nil;
}

- (BOOL)hasField:(NSString*)field forTable:(NSString*)table application:(FWApplication*)application path:(NSString*)path
{
    NSString* sql = [NSString stringWithFormat:
                     @"select count(*)"
                     " from SQLITE_MASTER"
                     " where TYPE='table' and NAME='%@' and SQL like '%%(%%%@%%)%%'",
                     table, field];
    
    NSArray* result = FWDBGetRecords(sql, kPI2TableDataSQL, nil, path, application, self, NO);
    NSNumber* count = (result.count == 1) ? [result objectAtIndex:0] : nil;

    XLog(@"LI:%@: Searching for DB entity; table = %@; field = %@; count = %@", application.resourceName, table, field, count);
        
    return count && (count.intValue == 1);
}

- (BOOL)testPI2DB:(FWApplication*)application path:(NSString*)path
{
    // Check the database appears to work
    NSString* test = @"SELECT Z_ENT, Z_NAME FROM Z_PRIMARYKEY";

    NSArray* values = FWDBGetRecords(test, kPI2VersionSQL, nil, path, application, self, NO);
    if (!values)
        return NO;

    dbVersion = PIDatabaseVersionUnknown;
    dbEntityKeys = [[NSMutableDictionary alloc] init];
    for (NSArray* item in values) {
        [dbEntityKeys setValue:[item objectAtIndex:0] forKey:[item objectAtIndex:1]];
    }

    if ([self hasField:@"Z_15CALENDARITEMS1" forTable:@"Z_15TAGS" application:application path:path])
        dbVersion = PIDatabaseVersion202;
    else if ([self hasField:@"Z_24EVENTS" forTable:@"Z_16EVENTS" application:application path:path])
        dbVersion = PIDatabaseVersion250;
    
    DebugLog(@"LI:PIPlugin: Read PI2 entity keys = %@", dbEntityKeys);

    //NSString* metadata = @"SELECT Z_UUID, Z_PLIST FROM Z_METADATA";
    //FWDBGetRecords(metadata, kPI2MetaDataSQL, nil, path, application, self, NO);

    return (dbVersion != PIDatabaseVersionUnknown) &&
        [dbEntityKeys objectForKey:kPIEntityTypeTask] &&
        [dbEntityKeys objectForKey:kPIEntityTypeEvent];
}    

// ******************************************************************************************
#pragma mark -
#pragma mark Configuration and data management

static NSString* const kPublicAppRoot = @"/var/mobile";
static NSString* const kPI2DBPath = @"/Documents/pim.informantdata";
static NSString* const kPIPrefsPattern = @"/Library/Preferences/%@.plist";


- (BOOL)updateApplication:(FWApplication*)application
{
    if (self.isNativeOnly) {
        pocketInformantPresent = NO;
        [PIIntegration updateInfoFromPrefs:nil application:application];
        NSLog(@"LI:%@: Running in native-only mode", application.resourceName);
        return NO;
    }
    
    SBApplication* app = [PIPlugin findPocketInformantForApplication:application];
    if (!app) {
        XLog(@"LI:PIPlugin: No application found - marking PI missing");
        pocketInformantPresent = NO;

#ifdef DEVELOPMENT_MODE
        pocketInformantPresent = YES;
#endif
        
        return NO;
    }
    
    self.appURLScheme = app.urlScheme;
    self.appDisplayIdentifier = app.displayIdentifier;
    self.appBundleIdentifier = app.bundleIdentifier;
    
    // Check the database appears to work
    pocketInformantPresent = NO;
    if (![self testPI2DB:application path:application.databasePath])
        XLog(@"LI:PIPlugin: PI2 Database not readable at %@", application.databasePath);
    else
        pocketInformantPresent = YES;
    
    if (!pocketInformantPresent) {
        XLog(@"LI:PIPlugin: Not happy with DB - marking PI missing");
        return NO;
    }

    // Do we have a full version of PI?
    pocketInformantFull = ![self.appDisplayIdentifier isEqualToString:PILiteIdentifier];

#ifdef DEBUG
    // Get initial config
    NSDictionary* prefs = [NSDictionary dictionaryWithContentsOfFile:application.preferencesPath];
    DebugLog(@"LI:PIPlugin: PI Preferences on startup = %@", prefs);
#endif
    
    self.pocketInformantLastSeen = [NSDate date];
    
    // Record information about the application for the preferences components
    if (UIPasteboard* pasteBoard = [UIPasteboard pasteboardWithName:kPIStatusPasteboard create:YES]) {
        pasteBoard.persistent = YES;
        pasteBoard.items = [NSArray arrayWithObjects:
                            [NSDictionary dictionaryWithObject:application.databasePath forKey:kPIPasteboardDBPath],
                            [NSDictionary dictionaryWithObject:application.applicationPath forKey:kPIPasteboardAppPath],
                            [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:self.dbVersion] forKey:kPIPasteboardDBVersion],
                            [NSDictionary dictionaryWithObject:self.appDisplayIdentifier forKey:kPIPasteboardAppType],
                            nil];
    }

    return YES;
}

+ (BOOL)findDatabaseAtPath:(NSString*)path application:(FWApplication*)application appInfo:(SBApplication*)appInfo
{
    XLog(@"LI:PIPlugin: Searching for database; path = %@", path);

    NSFileManager* fileMgr = [[[NSFileManager alloc] init] autorelease];
    NSString* dbPath = [path stringByAppendingString:kPI2DBPath];
    if ([fileMgr isReadableFileAtPath:dbPath]) {
        // Found database at standard location
        XLog(@"LI:PIPlugin: Database found at standard location %@", dbPath);
        application.databasePath = dbPath;
        application.preferencesPath = [path stringByAppendingFormat:kPIPrefsPattern, appInfo.displayIdentifier];
        return YES;
    }

    dbPath = [kPublicAppRoot stringByAppendingString:kPI2DBPath];
    if ([fileMgr isReadableFileAtPath:dbPath]) {
        // Found database in an odd location!
        XLog(@"LI:PIPlugin: Database unexpectedly found at %@", dbPath);
        application.databasePath = dbPath;
        application.preferencesPath = [kPublicAppRoot stringByAppendingFormat:kPIPrefsPattern, appInfo.displayIdentifier];
        return YES;
    }
    
    // No database found
    XLog(@"LI:PIPlugin: No database found");
    return NO;
}

+ (SBApplication*)findPocketInformantForApplication:(FWApplication*)application
{
    Class cls = objc_getClass("SBApplicationController");
    SBApplicationController* ctr = [cls sharedInstance];
    
    SBApplication* app = nil;
    FWDatasourceType src = application.fwPlugin.datasourceType;

    if (!app && ((src == FWDatasourceTypePro) || (src == FWDatasourceTypeAutomatic))) {
        // Searching for PI Pro app
        XLog(@"LI:PIPlugin: Searching for Pro application: %@", PIProIdentifier);
        app = [ctr applicationWithDisplayIdentifier:PIProIdentifier];
    }

    if (!app && ((src == FWDatasourceTypeiPhone) || (src == FWDatasourceTypeAutomatic))) {
        // Searching for iPhone app
        XLog(@"LI:PIPlugin: Searching for iPhone application: %@", PIiPhoneIdentifier);
        app = [ctr applicationWithDisplayIdentifier:PIiPhoneIdentifier];
    }
    
    if (!app && ((src == FWDatasourceTypeLite) || (src == FWDatasourceTypeAutomatic))) {
        // Searching for PI Go! app
        XLog(@"LI:PIPlugin: Searching for Go! application: %@", PILiteIdentifier);
        app = [ctr applicationWithDisplayIdentifier:PILiteIdentifier];
    }
    
    if (!app) {
        XLog(@"LI:PIPlugin: Couldn't find application anywhere!");
        return nil;
    }
    else
        XLog(@"LI:PIPlugin: Discovered application: %@", app);
    
    NSString* path = [app.path stringByDeletingLastPathComponent];
    application.applicationPath = app.path;
    application.preferencesPath = [path stringByAppendingFormat:kPIPrefsPattern, app.displayIdentifier];
    
    // Find the database. This will tell us which version of PI we are using
    if (![PIPlugin findDatabaseAtPath:path application:application appInfo:app]) {
        XLog(@"LI:PIPlugin: Cannot find database");
        return nil;
    }

    // Set information for the plugin
    application.pluginBundle = application.fwPlugin.liPlugin.bundle;
    NSString* baseName = [[[application.pluginBundle bundlePath] lastPathComponent]
                          stringByReplacingOccurrencesOfString:@".bundle" withString:@".plist"];
    application.pluginPreferencesPath = [kPublicAppRoot stringByAppendingFormat:@"/Library/Preferences/%@", baseName];
    
    XLog(@"LI:PIPlugin: Application path = %@", application.applicationPath);
    XLog(@"LI:PIPlugin: Application preferences path = %@", application.preferencesPath);
    XLog(@"LI:PIPlugin: Application database path = %@", application.databasePath);
    XLog(@"LI:PIPlugin: Application plugin preferences path = %@", application.pluginPreferencesPath);
    XLog(@"LI:PIPlugin: Plugin bundle: %@", application.pluginBundle);
    
    return app;
}

- (BOOL)application:(FWApplication*)application shouldUpdateForDate:(NSDate*)date
{
    return NO;
}

@end
